﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PSE.Communication.Fix.Common;
using System.Threading;
using PSE.Framework.Communication.Client.XmlHttp;
using PSE.Communication.Fix.Common.Fixml;
using System.Xml.Serialization;
using System.IO;
using System.Xml;
using System.Collections.Specialized;
using System.Configuration;
using PSE.Framework.Cryptography;

namespace PSE.Communication.Fix.Client
{
    public class XmlHttpClient
    {
        private XmlHttpOutputClient _xmlHttpOutputClient;
        private Authentication _authentication;
        private string _sessionName;
        private string _action;
        private static XmlHttpClient _client;
        private static object _locker = new object();

        public static XmlHttpClient GetInstance(string sessionName)
        {
            lock (_locker)
            {
                if (_client == null)
                    _client = new XmlHttpClient(sessionName);    
            }

            return _client;
        }

        public XmlHttpClient(string sessionName)
        {
            FixConfigurationSection fixConfiguration = (FixConfigurationSection)ConfigurationManager.GetSection("fixConfiguration");

            if (fixConfiguration != null)
            {
                foreach (FixmlSessionConfigurationElement item in fixConfiguration.FixmlSessions)
                {
                    if (item.Name == sessionName)
                    {
                        _xmlHttpOutputClient = new XmlHttpOutputClient();
                        _authentication = new Authentication();
                        _sessionName = sessionName;
                        
                        _xmlHttpOutputClient.Open(sessionName);

                        _action = "Action1";

                        Authenticate(item.User, item.Password);

                        break;
                    }
                }

                if (_xmlHttpOutputClient == null)
                    throw new Exception(string.Format("Cannot find fixml session with name '{0}' in the config file", sessionName));
            }
            else
                throw new Exception("Section fixConfiguration not found in the config file");
        }

        private void Authenticate(string user, string password)
        {
            _authentication.Logon = new Logon();
            _authentication.Logon.User = user;
            _authentication.Logon.Data = RijndaelCryptographer.DecryptString(password);
            _authentication.Logon.DataLength = _authentication.Logon.Data.Length;

            NameValueCollection formData = new NameValueCollection();
            formData["message"] = Serialize(typeof(Authentication), _authentication);

            object message = _xmlHttpOutputClient.Send(formData, _action);

            Authentication response = (Authentication)Deserialize(typeof(Authentication), message);

            if (response.Logon != null)
            {
                _authentication.Logon.Data = response.Logon.Data;
                _authentication.Logon.DataLength = response.Logon.DataLength;
            }
            else if (response.Logout != null)
            {
                throw new Exception(string.Format("an error occurred while logging in with the User {0}. Original error: {1}", user, response.Logout.Message));
            }
            else
            {
                MessageReject reject = (MessageReject)Deserialize(typeof(MessageReject), message);

                if (reject != null && reject.BusinessMessageReject != null)
                    throw new Exception(string.Format("Unable to authenticate to the server configured in the session {0}. Original error: {1}", _sessionName, reject.BusinessMessageReject.Message));
                else
                    throw new Exception(string.Format("Unable to authenticate to the server configured in the session {0}", _sessionName));
            }
        }

        public AllocationAck Send(Allocation allocation, out MessageReject reject)
        {
            if (allocation == null)
                throw new ArgumentNullException("allocation");

            AllocationAck response = null;
            reject = null;

            allocation.Instruction.Token = _authentication.Logon.Data;
            allocation.Instruction.TokenLength = _authentication.Logon.DataLength;

            NameValueCollection formData = new NameValueCollection();
            formData["message"] = Serialize(typeof(Allocation), allocation);

            object message = _xmlHttpOutputClient.Send(formData, _action);

            if (message != null)
            {
                string m = message.ToString();

                if (m.Contains("AllocInstrctnAck"))
                {
                    response = (AllocationAck)Deserialize(typeof(AllocationAck), message);
                }
                else if (m.Contains("Reject") || m.Contains("BizMsgRej"))
                {
                    reject = (MessageReject)Deserialize(typeof(MessageReject), message);
                }
            }

            return response;
        }

        private string Serialize(Type type, object message)
        {
            StringBuilder sb = new StringBuilder();

            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Encoding = System.Text.Encoding.UTF8;
            settings.Indent = true;
            settings.OmitXmlDeclaration = true;

            XmlWriter writer = XmlWriter.Create(sb, settings);

            XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
            namespaces.Add(string.Empty, string.Empty);

            XmlSerializer s = new XmlSerializer(type);

            s.Serialize(writer, message, namespaces);

            return sb.ToString();
        }

        private object Deserialize(Type type, object message)
        {
            XmlSerializer s = new XmlSerializer(type);

            StringReader reader = new StringReader(Convert.ToString(message));

            return s.Deserialize(reader);
        }
    }
}